package check

import (
	"crypto/tls"
	"daemon/conf"
	"daemon/utils"
	"fmt"
	"github.com/robfig/cron/v3"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var Entryid []cron.EntryID

func StartCheck() {
	entryIds := make([]cron.EntryID, 0)
	CheckInterval := conf.V.GetString("CheckInterval")
	var every string
	every = fmt.Sprintf("@every %s", CheckInterval)
	if isValidCheckInterval(CheckInterval) {
		conf.Logger.Errorf("检测到 CheckInterval 配置小于 1 分钟，防止检测间隔太短导致无限重启铺码服务，守护进程将使用 3 分钟检测间隔时间启动.")
		every = fmt.Sprintf("@every %s", "3m")
	}
	checkId, err := utils.Cron.AddFunc(every, checkIng)
	if err != nil {
		conf.Logger.Errorf("添加定时任务失败: %v, 请检查 CheckInterval 配置, 支持单位： h m s 代表时、分、秒, 单位需要小写", err)
		conf.Logger.Infoln("以默认周期(3分钟)启动定时任务")
		checkId, err = utils.Cron.AddFunc("@every 3m", checkIng)
	}
	entryIds = append(entryIds, checkId)
	Entryid = entryIds
}

func checkIng() {
	daemonStatus := conf.V.GetString("DaemonStatus")
	daemonStatus = strings.ToLower(daemonStatus)
	if daemonStatus != "on" {
		conf.Logger.Infof("DaemonStatus 值是 %s, 非 on 处于暂停检测状态, 不对铺码服务进行检测. ", daemonStatus)
		return
	}
	url := conf.V.GetString("ProbeAddress")
	srvList := conf.V.GetStringSlice("ProcessList")
	//fmt.Println("srvList:", srvList)

	tr := &http.Transport{
		//跳过SSL证书验证
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	// 检查 http 存活接口
	client := http.Client{
		Timeout:   5 * time.Second,
		Transport: tr,
	}
	resp, err := client.Get(url)
	if err != nil {
		//fmt.Println("http_probe err:", err.Error())
		conf.Logger.Errorf("http 存活接口失败: %s, 重新启动铺码服务.", url)
		reStart()
		return
	}
	conf.Logger.Infof("http 存活接口成功: %s", url)
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		fmt.Println("http_probe http status error:", resp.StatusCode)
		conf.Logger.Errorf("http 存活接口失败: %s, ErrorCode: %d", url, resp.StatusCode)
	}

	// 检查进程
	for _, srv := range srvList {
		if !checkProcess(srv) {
			reStart()
			break
		}
	}
}

func fileExists(filename string) bool {
	conf.Logger.Infoln("filename:", filename)
	info, err := os.Stat(filename)
	if err != nil {
		return false
	}
	return !info.IsDir()
}

func reStart() {
	// 系统盘符
	sysDrive := os.Getenv("SystemDrive")
	sysDrive = sysDrive + string(os.PathSeparator)
	// 部署铺码程序的盘符
	deployDrive := os.Getenv("deploy_drive")
	deployDrive = deployDrive + string(os.PathSeparator)

	// 进程守护路径
	daemonPath := filepath.Join(deployDrive, "software", "daemon", "startup.bat")
	// 开机自启路径
	startMenuPath := filepath.Join(sysDrive, "ProgramData", "Microsoft", "Windows", "Start Menu", "Programs", "StartUp", "startup.bat")
	// 桌面启动文件路径
	desktopPath := filepath.Join(sysDrive, "Users", "Public", "Desktop", "startup.bat")

	var startFullPath string
	// 挨个判断启动文件是否存在
	if fileExists(daemonPath) {
		startFullPath = daemonPath
	} else if fileExists(startMenuPath) {
		startFullPath = startMenuPath
	} else if fileExists(desktopPath) {
		startFullPath = desktopPath
	} else {
		fmt.Println("检测到服务异常，但未找到启动文件 startup.bat 请检查启动文件是否存在!")
		conf.Logger.Errorln("检测到服务异常，未找到启动文件 startup.bat 请检查启动文件是否存在!")
		return
	}

	// 将 \ 替换为 /，避免转义字符问题
	//startFullPath = strings.Replace(startFullPath, `\`, "/", -1)
	conf.Logger.Info("startFullPath:", startFullPath)

	// 路径中没有空格时用这个就可以了
	//cmd := exec.Command("cmd", "/C", fmt.Sprintf("start %s", startFullPath))

	/*
		    // 路径中有空格时需要加双引号，但是加上双引号拼接时，exex.Command 在构造命令时，会自动把双引号转义
		    // 转义后有斜杠，cmd执行时无法识别斜杠，所以无法使用
			cmdPath := fmt.Sprintf(`start "" "%s"`, startFullPath)
			conf.Logger.Infoln("cmdPath:", cmdPath)
			fmt.Println("cmdPath:", cmdPath)
			//cmd := exec.Command("cmd", "/C", cmdPath)
	*/

	// 最终还是使用 pws, 它支持空格路径
	cmd := exec.Command(sysDrive+"/Program Files/PowerShell/7/pwsh.exe", "-Command", fmt.Sprintf(`Start-Process "%s"`, startFullPath))

	//cmd := exec.Command("cmd", "/C", `start c:\Users\Public\Desktop\startup.bat`)
	//cmd.Stdin = os.Stdin
	// 设置工作目录（可选）
	//cmd.Dir = filepath.Dir(startFullPath)

	// 执行命令并捕获输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		conf.Logger.Errorf("执行失败: %v\n", err)
		conf.Logger.Errorf("命令执行失败:\n%s\n", string(output))
		return
	}

	// 打印命令输出
	//fmt.Printf("命令执行失败:\n%s\n", string(output))

}

func checkProcess(name string) bool {
	para := fmt.Sprintf("imagename eq %s", name)
	command := exec.Command("cmd.exe", "/c", "tasklist", "/fi", para)
	// 隐藏窗口的关键配置
	command.SysProcAttr = &syscall.SysProcAttr{
		HideWindow: true, // 隐藏运行窗口
	}

	command.Stdout = nil // 禁止输出到终端
	command.Stderr = nil // 禁止错误输出到终端

	out, err := command.CombinedOutput()
	command.Stdout = nil // 禁止输出到终端
	command.Stderr = nil // 禁止错误输出到终端
	if err != nil {
		conf.Logger.Errorf("进程检查命令执行出借: %s", err.Error())
		return false
	}
	if strings.Contains(string(out), name) {
		//fmt.Println(string(out))
		return true
	}
	conf.Logger.Errorf("未检测到 %s 进程存在, 重新启动铺码服务.", name)
	return false
}

func isValidCheckInterval(CheckInterval string) bool {
	// 首先判断长度, 大于3位或者单位不是s的不进行校验
	if len(CheckInterval) > 3 || !strings.HasSuffix(CheckInterval, "s") {
		conf.Logger.Infof("CheckInterval 配置: %s, 大于3位或者单位不是s的不进行校验.", CheckInterval)
		return false
	}

	// 去掉最后一个字符
	numStr := CheckInterval[:len(CheckInterval)-1]

	// 将数字部分转换为整数
	number, err := strconv.Atoi(numStr)
	if err != nil {
		conf.Logger.Errorf("CheckInterval 转换失败: %s", err.Error())
		return false
	}
	if number < 60 {
		conf.Logger.Infof("CheckInterval 配置小于 60 秒")
		return true
	}
	return false
}
